#include <iostream>
#include <sstream>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TSSLSocket.h>
#include "FirstService.h"

using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using namespace thrift::test;

///////////////////////////////////////////////////////////////////////////////////////////////////
template<typename Proto>
class TPedanticProtocol : public Proto
{
public:
    TPedanticProtocol(std::shared_ptr<TTransport>& transport) : Proto(transport), m_last_seqid((std::numeric_limits<int32_t>::max)() - 10)
    {

    }

    virtual uint32_t writeMessageBegin_virt(const std::string& name, const TMessageType messageType, const int32_t in_seqid) override
    {
        int32_t seqid = in_seqid;
        if (!seqid)
        {
            // this is typical for normal cpp generated code
            seqid = ++m_last_seqid;
        }

        return Proto::writeMessageBegin_virt(name, messageType, seqid);
    }

    virtual uint32_t readMessageBegin_virt(std::string& name, TMessageType& messageType, int32_t& seqid) override
    {
        uint32_t result = Proto::readMessageBegin_virt(name, messageType, seqid);
        if (seqid != m_last_seqid)
        {
            std::stringstream ss;
            ss << "ERROR: send request with seqid " << m_last_seqid << " and got reply with seqid " << seqid;
            throw std::logic_error(ss.str());
        }

        return result;
    }

private:
    int32_t m_last_seqid;
};

int main(int argc, char** argv)
{
    std::string sCACertPath = "cert.v3/ca.crt";
    std::string sServerHost = "127.0.0.1";

    ///
    std::shared_ptr<TSSLSocketFactory> factory;
    factory = std::shared_ptr<TSSLSocketFactory>(new TSSLSocketFactory());
    factory->loadTrustedCertificates(sCACertPath.c_str());
    factory->authenticate(true);
    factory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");

    std::shared_ptr<TSocket> socket;
    socket = factory->createSocket(sServerHost, 9090);

    ///
    std::shared_ptr<TTransport> transport;
    transport = std::make_shared<TBufferedTransport>(socket);

    typedef TPedanticProtocol<TBinaryProtocol> TPedanticBinaryProtocol;
    std::shared_ptr<TProtocol> protocol;
    protocol = std::make_shared<TPedanticBinaryProtocol>(transport);

    // Connection info
    std::cout << "Connecting (buffered/binary" << ") to: " << sServerHost << ":" << socket->getPort() << std::endl;

    FirstServiceClient testClient(protocol);

    try
    {
        transport->open();
        std::string s;
        testClient.testString(s, "Test");
        std::cout << "testString(\"Test\")" << " = " << s << std::endl;
        if (s != "Test")
        {
            std::cout << "error" << std::endl;
        }
    }
    catch (TTransportException& ex)
    {
        std::cout << "Connect failed: " << ex.what() << std::endl;

        return 0;
    }

    transport->close();

    return 0;
}
